package com.desaysv.someip;

import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;

import androidx.annotation.NonNull;

import java.io.ByteArrayOutputStream;
import java.util.Arrays;

/**
 * @author uidq5208
 * @version 1.0
 * @date 2023-3-6
 * @since 20230306
 */
public class SomeIpMessager implements Parcelable {

    /**
     * serverId的长度
     */
    private static final int SERVICE_ID_LENGTH = 2;
    /**
     * 方法id的长度
     */
    private static final int METHOD_ID_LENGTH = 2;
    /**
     * messageId的长度
     */
    private static final int MESSAGE_ID_LENGTH = SERVICE_ID_LENGTH + METHOD_ID_LENGTH;
    /**
     * 协议内容长度
     */
    private static final int PROCOTOL_LENGTH = 4;
    /**
     * clientid长度
     */
    private static final int CLIENT_ID_LENGTH = 2;
    /**
     * sessionId长度
     */
    private static final int SESSION_ID_LENGTH = 2;
    /**
     * requestId长度
     */
    private static final int REQUEST_ID_LENGTH = CLIENT_ID_LENGTH + SESSION_ID_LENGTH;
    /**
     * 协议版本号长度
     */
    private static final int PROCOTOL_VERSION_LENGTH = 1;
    /**
     * interface版本长度
     */
    private static final int INTERFACE_VERSION_LENGTH = 1;
    /**
     * 消息类型长度
     */
    private static final int MSG_TYPE_LENGTH = 1;
    /**
     * 返回码长度
     */
    private static final int RETURN_CODE_LENGTH = 1;
    /**
     * foot的长度
     */
    private static final int FOOT_END_LENGTH = PROCOTOL_VERSION_LENGTH + INTERFACE_VERSION_LENGTH + MSG_TYPE_LENGTH + RETURN_CODE_LENGTH;
    /**
     * 记录会话id
     */
    private static int mSessionId = 0;

    /**
     * 私有构造函数，避免直接new
     */
    public SomeIpMessager() {
    }

    /**
     * 服务id
     */
    private int serviceId;
    /**
     * 方法id
     */
    private int methodId;
    /**
     * 消息长度
     */
    private int length;
    /**
     * 客户端id
     */
    private int clientId;
    /**
     * 当前消息的会话id
     */
    private int sessionId;

    /**
     * 协议版本
     */
    private int protocolVersion;
    /**
     * 接口版本
     */
    private int interfaceVersion;
    /**
     * 消息类型
     */
    private MsgType messageType = MsgType.REQUEST;
    /**
     * 响应码
     */
    private int returnCode = 0x00;
    /**
     * 信息内容
     */
    private byte[] payload;


    /**
     * @return serviceId
     */
    public int getServiceId() {
        return serviceId;
    }

    /**
     * @return methodId
     */
    public int getMethodId() {
        return methodId;
    }

    /**
     * @return length
     */
    public int getLength() {
        return length;
    }

    /**
     * @return sessionId
     */
    public int getSessionId() {
        return sessionId;
    }

    /**
     * @return messageType
     */
    public MsgType getMessageType() {
        return messageType;
    }

    /**
     * @return returnCode
     */
    public int getReturnCode() {
        return returnCode;
    }

    /**
     * @return info
     */
    public byte[] getPayload() {
        return payload;
    }

    @NonNull
    @Override
    public String toString() {
        return "Messager{" +
                "serviceId=" + serviceId +
                ", methodId=" + methodId +
                ", length=" + length +
                ", clientId=" + clientId +
                ", sessionId=" + sessionId +
                ", protocolVersion=" + protocolVersion +
                ", interfaceVersion=" + interfaceVersion +
                ", messageType=" + messageType +
                ", returnCode=" + returnCode +
                ", info=" + Arrays.toString(payload) +
                '}';
    }

    /**
     * 根据协议结构解包
     *
     * @param response 响应信息
     * @return SomeIpMessager
     */
    public static SomeIpMessager decoder(byte[] response) {
        return decoder(response, false);
    }


    /**
     * @param response       响应信息
     * @param isLittleEndian int值高低位解析设置
     * @return SomeIpMessager
     */
    public static SomeIpMessager decoder(byte[] response, boolean isLittleEndian) {
        SomeIpMessager messager = new SomeIpMessager();
        byte[] bytes = new byte[SERVICE_ID_LENGTH];
        int index = 0;

        //serviceid
        System.arraycopy(response, index, bytes, 0, bytes.length);
        messager.serviceId = byteToInt(bytes, isLittleEndian);
        index = bytes.length;

        //methodId
        System.arraycopy(response, index, bytes, 0, bytes.length);
        messager.methodId = byteToInt(bytes, isLittleEndian);
        index = index + bytes.length;

        //length
        bytes = new byte[PROCOTOL_LENGTH];
        System.arraycopy(response, index, bytes, 0, bytes.length);
        messager.length = byteToInt(bytes, isLittleEndian);
        index = index + bytes.length;

        //长度验证
        if (MESSAGE_ID_LENGTH + PROCOTOL_LENGTH + messager.length > response.length) {
            return null;
        }

        //clientid
        bytes = new byte[CLIENT_ID_LENGTH];
        System.arraycopy(response, index, bytes, 0, bytes.length);
        messager.clientId = byteToInt(bytes, isLittleEndian);
        index = index + bytes.length;

        //sessionId
        System.arraycopy(response, index, bytes, 0, bytes.length);
        messager.sessionId = byteToInt(bytes, isLittleEndian);
        index = index + bytes.length;

        //procotol version
        messager.protocolVersion = response[index++];
        //interface version
        messager.interfaceVersion = response[index++];
        //msg type
        messager.messageType = cover(response[index++]);
        //return code
        messager.returnCode = response[index++];

        //payload length-clientid-sessionid-4[1-1-1-1]
        int payloadLength = messager.length - (REQUEST_ID_LENGTH + FOOT_END_LENGTH);
        //payloadLength == 0 means no payload
        if (payloadLength > 0) {
            messager.payload = new byte[payloadLength];
            System.arraycopy(response, index, messager.payload, 0, messager.payload.length);
        }

        return messager;
    }

    /**
     * 序列化构造函数
     *
     * @param in Parcel
     */
    protected SomeIpMessager(Parcel in) {
        readFromParcel(in);
    }

    /**
     * 序列化构造器
     */
    public static final Creator<SomeIpMessager> CREATOR = new Creator<SomeIpMessager>() {
        @Override
        public SomeIpMessager createFromParcel(Parcel in) {
            return new SomeIpMessager(in);
        }

        @Override
        public SomeIpMessager[] newArray(int size) {
            return new SomeIpMessager[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(serviceId);
        dest.writeInt(methodId);
        dest.writeInt(length);
        dest.writeInt(clientId);
        dest.writeInt(sessionId);
        dest.writeInt(protocolVersion);
        dest.writeInt(interfaceVersion);
        dest.writeInt(returnCode);
        dest.writeByteArray(payload);
    }

    public void readFromParcel(Parcel in) {
        serviceId = in.readInt();
        methodId = in.readInt();
        length = in.readInt();
        clientId = in.readInt();
        sessionId = in.readInt();
        protocolVersion = in.readInt();
        interfaceVersion = in.readInt();
        returnCode = in.readInt();
        payload = in.createByteArray();
    }

    /**
     * 构造器
     */
    public static class Buidler {
        /**
         * 通过构造器初始化函数
         */
        private final SomeIpMessager mMsg = new SomeIpMessager();

        /**
         * @param serviceId 服务id
         */
        public Buidler(int serviceId) {
            //根据实际项目配置
            mMsg.protocolVersion = 1;
            mMsg.interfaceVersion = 1;
            mMsg.serviceId = serviceId;
            mMsg.sessionId = mSessionId++;
        }

        /**
         * @param methodId 对应方法的id
         * @return Buidler
         */
        public Buidler setMethodId(int methodId) {
            mMsg.methodId = methodId;
            return this;
        }

        /**
         * @param clientId 客户端id
         * @return Buidler
         */
        public Buidler setClientId(int clientId) {
            mMsg.clientId = clientId;
            return this;
        }

        /**
         * @param msgType 消息类型，默认request
         * @return Buidler
         */
        public Buidler setMsgType(MsgType msgType) {
            mMsg.messageType = msgType;
            return this;
        }

        public MsgType getMsgType() {
            return mMsg.messageType;
        }

        /**
         * 获取当前回话的id，请求的id必须和响应的id相对应
         *
         * @return sessionId
         */
        public int getSessionId() {
            return mMsg.sessionId;
        }

        /**
         * @return 返回构造的someip协议数据
         */
        public byte[] builder() {
            return builder(new byte[0], false);
        }

        /**
         * @param isLittleEndian 构造时int类型的高度位配置
         * @return 返回构造的someip协议数据
         */
        public byte[] builder(boolean isLittleEndian) {
            return builder(new byte[0], isLittleEndian);
        }

        /**
         * @param infos payload载荷
         * @return 返回构造的someip协议数据
         */
        public byte[] builder(String infos) {
            if (!TextUtils.isEmpty(infos)) {
                return builder(infos.getBytes(), false);
            } else {
                return builder(new byte[0], false);
            }
        }

        /**
         * @param infos          payload载荷
         * @param isLittleEndian 构建时int数据的高低位配置
         * @return 返回构造的someip协议数据
         */
        public byte[] builder(String infos, boolean isLittleEndian) {
            if (!TextUtils.isEmpty(infos)) {
                return builder(infos.getBytes(), isLittleEndian);
            } else {
                return builder(new byte[0], isLittleEndian);
            }
        }

        /**
         * 根据 协议结构封包
         *
         * @param infos          payload载荷
         * @param isLittleEndian 构建时int数据高低位配置
         * @return 返回构造的someip协议数据
         */

        public byte[] builder(byte[] infos, boolean isLittleEndian) {
            if (infos != null && infos.length > 0) {
                mMsg.payload = infos;
            }
            byte[] data = new byte[0];
            try (ByteArrayOutputStream baos = new ByteArrayOutputStream(MESSAGE_ID_LENGTH)) {
                //message Id 注意配置高低位
                baos.write(intToByteArray(mMsg.serviceId, SERVICE_ID_LENGTH, isLittleEndian));
                baos.write(intToByteArray(mMsg.methodId, METHOD_ID_LENGTH, isLittleEndian));

                ByteArrayOutputStream baosFoot = new ByteArrayOutputStream(PROCOTOL_LENGTH);
                //request id
                baosFoot.write(intToByteArray(mMsg.clientId, CLIENT_ID_LENGTH, isLittleEndian));
                baosFoot.write(intToByteArray(mMsg.sessionId, SESSION_ID_LENGTH, isLittleEndian));
                baosFoot.write(intToByteArray(mMsg.protocolVersion, PROCOTOL_VERSION_LENGTH, isLittleEndian));
                baosFoot.write(intToByteArray(mMsg.interfaceVersion, INTERFACE_VERSION_LENGTH, isLittleEndian));
                baosFoot.write(intToByteArray(mMsg.messageType.getValue(), MSG_TYPE_LENGTH, isLittleEndian));
                baosFoot.write(intToByteArray(mMsg.returnCode, RETURN_CODE_LENGTH, isLittleEndian));
                if (mMsg.payload != null && mMsg.payload.length > 0) {
                    //payload
                    baosFoot.write(mMsg.payload);
                }

                byte[] foot = baosFoot.toByteArray();
                baosFoot.close();

                //length
                mMsg.length = foot.length;
                baos.write(intToByteArray(mMsg.length, PROCOTOL_LENGTH, isLittleEndian));
                baos.write(foot);

                data = baos.toByteArray();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return data;
        }

    }


    public enum MsgType {
        REQUEST(0x00),
        REQUEST_NO_RETURN(0x01),
        NOTIFICATION(0x02),
        REQUEST_ACK(0x40),
        REQUEST_NO_RETURN_ACK(0x41),
        NOTIFICATION_ACK(0x42),
        RESPONSE(0x80),
        ERROR(0x81),
        RESPONSE_ACK(0xC0),
        ERROR_AC(0xC1);
        private int value;

        MsgType(int type) {
            this.value = type;
        }

        public int getValue() {
            return value;
        }
    }

    public static MsgType cover(int type) {
        for (MsgType value : MsgType.values()) {
            if (value.getValue() == type) {
                return value;
            }
        }
        return MsgType.REQUEST;
    }


    protected static byte[] intToByteArray(int value, int size, boolean isLittleEndian) {
        return longToByteArray(value, size, isLittleEndian);
    }


    private static final int BIT_LENGTH = 8;

    /**
     * @param value
     * @param size
     * @param isLittleEndian 高低位排列
     * @return
     */
    protected static byte[] longToByteArray(long value, int size, boolean isLittleEndian) {
        byte[] result = new byte[size];
        byte shift;
        for (int i = 0; i < size; i++) {
            shift = (byte) ((value >>> (BIT_LENGTH * i)) & 0xff);
            if (isLittleEndian) {
                result[i] = shift;
            } else {
                result[size - i - 1] = shift;
            }
        }
        return result;
    }

    /**
     * Convert byte array to string
     *
     * @param content byte array
     */
    public static String byteArrayToHexString(byte[] content) {
        if (content == null) {
            return "null";
        } else {
            StringBuilder builder = new StringBuilder();
            for (byte value : content) {
                builder.append("0x").append(Integer.toHexString(value & 0xff)).append(", ");
            }
            return builder.substring(0, builder.length() - 2);
        }
    }

    public static int byteToInt(byte[] bytes, boolean isLittleEndian) {
        return (int) byteToLong(bytes, isLittleEndian);
    }

    private static long byteToLong(byte[] bytes, boolean isLittleEndian) {
        long result = 0;
        for (int i = 0; i < bytes.length; i++) {
            if (isLittleEndian) {
                result |= ((long) (bytes[i] & 0xff) << (BIT_LENGTH * i));
            } else {
                result |= ((long) (bytes[i] & 0xff) << (BIT_LENGTH * (bytes.length - i - 1)));
            }
        }
        return result;
    }
}
